﻿//   LoggingUtilties.cs
//   Copyright (c) 2012 Eswar Prakash
// 
//    This library is free software; you can redistribute it and/or modify  
//    it  under the terms of the GNU Lesser General Public License version  
//    2.1 as published by the Free Software Foundation.                     
//                                                                          
//    This library is distributed in the hope that it will be useful, but   
//    WITHOUT ANY WARRANTY; without even the implied warranty of            
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     
//    Lesser General Public License for more details.                       
//                                                                          
//    You should have received a copy of the GNU Lesser General Public      
//    License along with this library; if not, write to the Free Software   
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  
//    USA                                                                 
// 

namespace CKSAS.Core.Diagnostics
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.SharePoint.Administration;
    using System.Text.RegularExpressions;
    using System.Diagnostics;
    using Utilities;

    /// <summary>
    /// Contains methods to setup and use SharePoint ULS logging
    /// </summary>
    public static class LoggingUtilities
    {
        /// <summary>
        /// Cache of diagnostic areas
        /// </summary>
        private static Dictionary<DiagnosticsAreas, string> areaCache =
            new Dictionary<DiagnosticsAreas, string>();

        /// <summary>
        /// Cache of category names
        /// </summary>
        private static Dictionary<DiagnosticCategories, string> categoryCache =
            new Dictionary<DiagnosticCategories, string>();

        static LoggingUtilities()
        {
            // cache all diagnostic areas and category names
            var areaFields = typeof(DiagnosticsAreas)
                .GetFields()
                .Where(x => !x.IsSpecialName);
            foreach (var field in areaFields)
            {
                // what is the title associated with the field
                string fieldTitle = String.Empty;
                var attrib = field.GetCustomAttributes(false)
                    .OfType<DiagnosticTitleAttribute>()
                    .FirstOrDefault();
                if (attrib != null)
                {
                    fieldTitle = attrib.Title;
                }
                else
                {
                    fieldTitle = field.Name.AsTitle();
                }

                areaCache.Add((DiagnosticsAreas)Enum.Parse(typeof(DiagnosticsAreas), field.Name),
                    fieldTitle);
            }

            var enumFields = typeof(DiagnosticCategories)
                .GetFields()
                .Where(x => !x.IsSpecialName);
            foreach (var field in enumFields)
            {
                // what is the title associated with the field
                string fieldTitle = String.Empty;
                var attrib = field.GetCustomAttributes(false)
                    .OfType<DiagnosticTitleAttribute>()
                    .FirstOrDefault();
                if (attrib != null)
                {
                    fieldTitle = attrib.Title;
                }
                else
                {
                    fieldTitle = field.Name.AsTitle();
                }

                categoryCache.Add((DiagnosticCategories)Enum.Parse(typeof(DiagnosticCategories), field.Name), 
                    fieldTitle);
            }
        }

        /// <summary>
        /// Writes the trace entry to ULS logs
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="area">The area.</param>
        /// <param name="category">The category.</param>
        /// <param name="severity">The severity.</param>
        public static void WriteTraceEntry(DiagnosticsAreas area, DiagnosticCategories category,
                                           TraceSeverity severity, string message)
        {
            try
            {
                DiagnosticsService.Local.WriteTrace(0, GetCategories(area)[category],
                                                    severity, message);
            }
            catch
            {
                // ignore
            }
        }

        /// <summary>
        /// Writes the trace entry to ULS logs
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="area">The area.</param>
        /// <param name="category">The category.</param>
        /// <param name="severity">The severity.</param>
        public static void WriteTraceEntry(DiagnosticsAreas area, DiagnosticCategories category,
                                           TraceSeverity severity, string message, params object[] parameters)
        {
            try
            {
                DiagnosticsService.Local.WriteTrace(0, GetCategories(area)[category],
                                                    severity, message, parameters);
            }
            catch
            {
                // ignore
            }
        }

        /// <summary>
        /// Writes the trace entry to ULS logs
        /// </summary>
        /// <param name="ex">The ex.</param>
        /// <param name="area">The area.</param>
        /// <param name="category">The category.</param>
        /// <param name="severity">The severity.</param>
        public static void WriteTraceEntry(DiagnosticsAreas area, DiagnosticCategories category,
                                           TraceSeverity severity, Exception exception)
        {
            try
            {
                StringBuilder exceptionMessageBuilder = new StringBuilder();
                exceptionMessageBuilder.AppendFormat("Exception of type {0} was thrown by the target",
                                                     exception.GetType().Name);
                exceptionMessageBuilder.AppendLine();
                exceptionMessageBuilder.AppendFormat("Message: {0}", exception.Message);
                exceptionMessageBuilder.AppendLine();
                exceptionMessageBuilder.AppendFormat("Stack trace: {0}", exception.StackTrace);
                exceptionMessageBuilder.AppendLine();

                DiagnosticsService.Local.WriteTrace(0, GetCategories(area)[category],
                                                    severity, exceptionMessageBuilder.ToString());
            }
            catch
            {
                // ignore
            }
        }


        /// <summary>
        /// Writes the event entry to the ULS logs
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="area">The area.</param>
        /// <param name="category">The category.</param>
        /// <param name="severity">The severity.</param>
        public static void WriteEventEntry(DiagnosticsAreas area, DiagnosticCategories category,
                                           EventSeverity severity, string message)
        {
            try
            {
                // Ensure all event sources exist
                EnsureEventSource();

                DiagnosticsService.Local.WriteEvent(0, GetCategories(area)[category],
                                                    severity, message);
            }
            catch (Exception ex)
            {
                // log and ignore
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable, ex);
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable,
                                "There was an error writing to the Windows event log. The original exception message is logged below.");
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable,
                                message);
            }
        }

        /// <summary>
        /// Writes the event entry to the ULS logs
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="area">The area.</param>
        /// <param name="category">The category.</param>
        /// <param name="severity">The severity.</param>
        public static void WriteEventEntry(DiagnosticsAreas area, DiagnosticCategories category,
                                           EventSeverity severity, string message, params object[] data)
        {
            try
            {
                // Ensure all event sources exist
                EnsureEventSource();

                DiagnosticsService.Local.WriteEvent(0, GetCategories(area)[category],
                                                    severity, message, data);
            }
            catch (Exception ex)
            {
                // log and ignore
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable, ex);
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable,
                                "There was an error writing to the Windows event log. The original exception message is logged below.");
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable,
                                message, data);
            }
        }

        /// <summary>
        /// Writes the event entry with exception details to ULS logs
        /// </summary>
        /// <param name="ex">The ex.</param>
        /// <param name="area">The area.</param>
        /// <param name="category">The category.</param>
        /// <param name="severity">The severity.</param>
        public static void WriteEventEntry(DiagnosticsAreas area, DiagnosticCategories category,
                                           EventSeverity severity, Exception exception)
        {
            StringBuilder exceptionMessageBuilder = new StringBuilder();
            exceptionMessageBuilder.AppendFormat("Exception of type {0} was thrown by the target",
                                                 exception.GetType().Name);
            exceptionMessageBuilder.AppendLine();
            exceptionMessageBuilder.AppendFormat("Message: {0}", exception.Message);
            exceptionMessageBuilder.AppendLine();
            exceptionMessageBuilder.AppendFormat("Stack trace: {0}", exception.StackTrace);
            exceptionMessageBuilder.AppendLine();

            try
            {
                // Ensure all event sources exist
                EnsureEventSource();

                DiagnosticsService.Local.WriteEvent(0, GetCategories(area)[category],
                                                    severity, exceptionMessageBuilder.ToString());
            }
            catch (Exception ex)
            {
                // log and ignore
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable, ex);
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable,
                                "There was an error writing to the Windows event log. The original exception message is logged below.");
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable,
                                exceptionMessageBuilder.ToString());
            }
        }

        /// <summary>
        /// Writes the event entry with exception details to ULS logs
        /// </summary>
        /// <param name="ex">The ex.</param>
        /// <param name="area">The area.</param>
        /// <param name="category">The category.</param>
        /// <param name="severity">The severity.</param>
        public static void WriteEventEntry(DiagnosticsAreas area, DiagnosticCategories category,
                                           EventSeverity severity, Exception exception, string additionalMessage)
        {
            StringBuilder exceptionMessageBuilder = new StringBuilder();
            exceptionMessageBuilder.AppendFormat("Exception of type {0} was thrown by the target",
                                                 exception.GetType().Name);
            exceptionMessageBuilder.AppendLine();
            exceptionMessageBuilder.AppendFormat("Message: {0}", exception.Message);
            exceptionMessageBuilder.AppendLine();
            exceptionMessageBuilder.AppendFormat("Stack trace: {0}", exception.StackTrace);
            exceptionMessageBuilder.AppendLine();
            exceptionMessageBuilder.AppendFormat("Additional message: {0}", additionalMessage);

            try
            {
                // Ensure all event sources exist
                EnsureEventSource();

                DiagnosticsService.Local.WriteEvent(0, GetCategories(area)[category],
                                                    severity, exceptionMessageBuilder.ToString());
            }
            catch (Exception ex)
            {
                // log and ignore
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable, ex);
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable,
                                "There was an error writing to the Windows event log. The original exception message is logged below.");
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable,
                                exceptionMessageBuilder.ToString());
            }
        }

        /// <summary>
        /// Writes the event entry with exception details to ULS logs
        /// </summary>
        /// <param name="ex">The ex.</param>
        /// <param name="area">The area.</param>
        /// <param name="category">The category.</param>
        /// <param name="severity">The severity.</param>
        public static void WriteEventEntry(DiagnosticsAreas area, DiagnosticCategories category,
                                           EventSeverity severity, Exception exception, string additionalMessage,
                                           params object[] data)
        {
            StringBuilder exceptionMessageBuilder = new StringBuilder();
            exceptionMessageBuilder.AppendFormat("Exception of type {0} was thrown by the target",
                                                 exception.GetType().Name);
            exceptionMessageBuilder.AppendLine();
            exceptionMessageBuilder.AppendFormat("Message: {0}", exception.Message);
            exceptionMessageBuilder.AppendLine();
            exceptionMessageBuilder.AppendFormat("Stack trace: {0}", exception.StackTrace);
            exceptionMessageBuilder.AppendLine();
            exceptionMessageBuilder.AppendFormat("Additional message: {0}", additionalMessage);

            try
            {
                // Ensure all event sources exist
                EnsureEventSource();

                DiagnosticsService.Local.WriteEvent(0, GetCategories(area)[category],
                                                    severity, exceptionMessageBuilder.ToString(), data);
            }
            catch (Exception ex)
            {
                // log and ignore
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable, ex);
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable,
                                "There was an error writing to the Windows event log. The original exception message is logged below.");
                WriteTraceEntry(area, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable,
                                exceptionMessageBuilder.ToString(), data);
            }
        }

        /// <summary>
        /// Gets the cached diagnostic category dictionary
        /// </summary>
        private static DiagnosticCategoryCollection GetCategories(DiagnosticsAreas parentArea)
        {
            // name of the default area
            var area = DiagnosticsService.Local.Areas[areaCache[parentArea]];
            if (area != null)
            {
                return new DiagnosticCategoryCollection(area.Categories);
            }

            return null;
        }

        /// <summary>
        /// Ensures that the Windows event log event source exists in the current machine
        /// </summary>
        private static void EnsureEventSource()
        {
            var areaNames = Enum.GetNames(typeof(DiagnosticsAreas));
            foreach (var areaName in areaNames)
            {
                string areaTitle = areaCache[(DiagnosticsAreas) Enum.Parse(typeof (DiagnosticsAreas), areaName)];
                var sourceExists = false;
                try
                {
                    sourceExists = EventLog.SourceExists(areaTitle);
                }
                catch (Exception ex)
                {
                    // log and ignore
                    WriteTraceEntry(DiagnosticsAreas.CKSAS, DiagnosticCategories.Diagnostics, TraceSeverity.Monitorable,
                                    ex);
                }

                // now create event source if the source doesn't exist
                if (!sourceExists)
                {
                    try
                    {
                        EventLog.CreateEventSource(new EventSourceCreationData(areaTitle, "Application"));
                    }
                    catch (Exception ex)
                    {
                        WriteTraceEntry(DiagnosticsAreas.CKSAS, DiagnosticCategories.Diagnostics,
                                        TraceSeverity.Monitorable, ex);
                    }
                }
            }
        }
    }
}